//build:20240715
//To simplify the use of the micro-light communication protocol for UART components, the UART is encapsulated in this worker. Users only need to subscribe to events in the eventcenter to listen to UART.
import log from './dxLogger.js'
import uart from './dxUart.js'
import gpio from './dxGpio.js'
import ble from './dxBle.js'
import common from './dxCommon.js'
import dxMap from './dxMap.js'
import bus from './dxEventBus.js'
import * as os from "os"
import std from './dxStd.js'
const map = dxMap.get('default')
const id = "{{id}}"
const options = map.get("__vgble__run_init" + id)
const timeout = 10
const longTimeout = 500


// Hard-written key
let BLE_KEY = [0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x45, 0x67, 0x89, 0x01]

// Connection records
let auth = [
    {
        random: [0],
        randomLen: 0,
        index: 0xff,
    },
    {
        random: [0],
        randomLen: 0,
        index: 0xff,
    },
    {
        random: [0],
        randomLen: 0,
        index: 0xff,
    },
]

function run() {
    uart.open(options.type, options.path, options.id)

    gpio.requestGpio(75)
    gpio.setFuncGpio(75, 0x04)
    os.sleep(10)
    gpio.setFuncGpio(75, 0x05)
    os.sleep(10)

    // Set the key
    if(options.bleKey){
        BLE_KEY = options.bleKey
    }

    // Set Bluetooth name and broadcast identifier
    ble.setBleConfig({name: options.bleName}, options.id)

    log.info('vg ble start......,id =', id)
    std.setInterval(() => {
        try {
            // Bluetooth standard protocol mode
            readPacket(true)
        } catch (error) {
            log.error(error)
        }
    }, 10)
}

function readPacket(noResult) {
    let buffer = ble.toHexadecimal(uart.receive(1, timeout, options.id))
    if (buffer.length <= 0) {
        return;
    }
    if (buffer == "55") {
        buffer = ble.toHexadecimal(uart.receive(1, timeout, options.id))
        if (buffer != "aa") {
            return;
        }
    } else {
        return;
    }
    let pack = {
        head: '55aa',
    };
    // Read command word (occupies 1 byte)
    let cmd = ble.toHexadecimal(uart.receive(1, timeout, options.id))
    pack.cmd = cmd;
    if (!noResult) {
        // Read the result word (occupies 1 byte)
        let result = ble.toHexadecimal(uart.receive(1, timeout, options.id))
        pack.result = result;
    }
    // The command header has been parsed; the length word (occupying 2 bytes) is now being read.
    let bufferLen1 = uart.receive(1, timeout, options.id)
    let bufferLen2 = uart.receive(1, timeout, options.id)
    // Parse the length word to obtain the length of the data field.
    let dataLen = parseInt(bufferLen1) + parseInt(bufferLen2) * 256

    // Read the specified data length based on the length word.
    pack.dlen = dataLen
    if (dataLen > 0) {
        buffer = uart.receive(dataLen, longTimeout, options.id)
        if (buffer === null) {
            return;
        }
        pack.data = Array.from(buffer);
    } else {
        pack.data = 0
    }

    // Read 1 byte of parity bit
    pack.crc = ble.toHexadecimal(uart.receive(1, timeout, options.id))
    // The current CRC checksum is incorrect and the function needs to be rewritten.
    // let crc = ble.genCrc(pack)
    // if (crc != parseInt(pack.crc, 16)) {
    //     log.error("Verification failed:" + JSON.stringify(pack) + "Correct checksum:" + crc)
    //     return;
    // }

    let res;
    switch (cmd) {
        case "07":
            // Get random numbers
            CMD07(pack);
            break;
        case "08":
            // Verify connection validity
            CMD08(pack);
            break;
        case "60":
            // Bluetooth reply, reply with the device information queried.
            CMD60(pack);
            break;
        case "0f":
            // Business logic, data pass-through
            res = {}
            // Connection identifier
            res.index = pack.data[pack.dlen - 1].toString(16).padStart(2, '0');
            // Pass-through data
            if (pack.dlen > 0) {
                let userId = pack.data.slice(5, -1).map((byte) => byte.toString(16).padStart(2, '0')).join('')
                res.data = userId
            }

            bus.fire(ble.VG.RECEIVE_MSG + options.id, res)
            break;
        case "fa":
            // Bluetooth reply, reply with the device information queried.
            CMDfa(pack);
            break;
        default:
            break;
    }
}

/**
 * Reply with a random number
 */
function CMD07(pack) {
    let data = pack.data
    let index = data[pack.dlen - 1].toString(16).padStart(2, '0');
    // Record connection identifier
    let curr = 0
    for (let i = 0; i < 3; i++) {
        if (auth[i].index == 0xff) {
            curr = i
            break;
        }
    }
    if (pack.dlen == 1 || data[0] == 0x10) {
        auth[curr].random = ble.getUrandom(16)
        auth[curr].randomLen = 16
    } else if (pack.dlen == 2 && data[0] == 0x20) {
        auth[curr].random = ble.getUrandom(32)
        auth[curr].randomLen = 32
    } else {
        return
    }
    auth[curr].index = index

    let content = auth[curr].random + index
    pack = { "head": "55aa", "cmd": "07", "result": "00", "dlen": auth[curr].randomLen + 1, "data": content.match(/.{2}/g).map(v => parseInt(v, 16)) }
    pack.crc = ble.genCrc(pack)
    data = pack.head + pack.cmd + pack.result + common.decimalToLittleEndianHex(pack.dlen, 2) + pack.data.map(v => v.toString(16).padStart(2, '0')).join("") + pack.crc.toString(16).padStart(2, '0')
    ble.send(data, options.id)
}

/**
 * Respond to external authorization (check the legitimacy of the connection)Respond to external authorization (check the legitimacy of the connection)
 */
function CMD08(pack) {
    let data = pack.data
    let index = data[pack.dlen - 1].toString(16).padStart(2, '0');
    let curr = -1
    pack = { "head": "55aa", "cmd": "08", "result": "00", "dlen": 1, "data": index, }
    for (let i = 0; i < 3; i++) {
        // Query the specified connection
        if (auth[i].index === index) {
            curr = i
            break;
        }
    }
    if (curr === -1) {
        log.error("extern auth failed");
        pack.result = "90"
    } else {
        // AES decryption
        let key = BLE_KEY
        let cipher = data.slice(1, -1)
        let plain = common.aes128EcbDecrypt(cipher, key)
        if (plain.map((byte) => byte.toString(16).padStart(2, '0'))
            .join('') !== auth[curr].random) {
            pack.result = "90"
        } else {
            auth[curr].index = 0xff;
        }
    }

    pack.data = pack.data.match(/.{2}/g).map(v => parseInt(v, 16))
    pack.crc = ble.genCrc(pack)
    data = pack.head + pack.cmd + pack.result + common.decimalToLittleEndianHex(pack.dlen, 2) + pack.data.map(v => v.toString(16).padStart(2, '0')).join("") + pack.crc.toString(16).padStart(2, '0')
    ble.send(data, options.id)
}

/**
 * Commands to filter Bluetooth Central mode
 */
function CMDIsBleControl(pack){
    if(pack.data.length < 5){
        return false
    }else if(pack.data[0] == 0x0a && pack.data[1] == 0x00 && pack.data[2] == 0x00 && (pack.data[3] >= 0x01 && pack.data[3] <= 0x05)){
        // Response to scanning device request
        // Stop scanning device response
        // Request a response from the connected device
        // Reply to disconnected device
        // Scanning responses for specific services
        return true
    }else if(pack.data[0] == 0x0a && pack.data[1] == 0x00 && pack.data[2] == 0x00 && (pack.data[3] >= 0x08 && pack.data[3] <= 0x0a)){
        // Feature-written reply
        // Subscription feature response
        // Feature Read Response
        return true
    }else if(pack.data[0] == 0x0a && pack.data[1] == 0x80 && (pack.data[2] >= 0x01 && pack.data[2] <= 0x04)){
        // Feature-written reply
        // Subscription feature response
        // Feature Read Response
        return true
    }else if(pack.data[0] == 0x0a && pack.data[1] == 0x80 && (pack.data[2] >= 0x06 || pack.data[2] <= 0x08)){
        // Feature-written reply
        // Subscription feature response
        // Feature Read Response
        return true
    }else{
        return false
    }
}

/**
 * Filter Bluetooth upgrade instructions
 */
function CMDIsBleUpdate(pack){
    if(pack.data.length < 5){
        return false
    }else if(pack.data[0] == 0x03 && pack.data[1] == 0x01 && pack.data[2] == 0x80 && (pack.data[3] == 0x01 || pack.data[3] == 0x02 || pack.data[3] == 0x03 || pack.data[3] == 0x04 || pack.data[3] == 0x05)){
        // pack.data[3] == 0x01 Reply to the command to enter Bluetooth upgrade mode 

        // pack.data[3] == 0x02 Reply to the command to send Bluetooth upgrade package description information

        // pack.data[3] == 0x03 Reply to sending Bluetooth upgrade package

        // pack.data[3] == 0x04 Reply to sending the Bluetooth upgrade completion command

        // pack.data[3] == 0x05 Reply to the command to install the upgrade package
        return true
    }else{
        return false
    }
}

/**
 * Get Bluetooth responses (response for querying Bluetooth information, response for whether Bluetooth configuration changes were successful).
 */
function CMD60(pack) {

    if(CMDIsBleControl(pack)){
        bus.fire("characterRead", pack)
        return
    }

    if(CMDIsBleUpdate(pack)){
        bus.fire("bleUpgrade", pack)
        return
    }

    let res = {}
    let data = pack.data.map(v => v.toString(16).padStart(2, '0'))
    if (data.slice(-1)[0] == 'fe') {
        data = data.slice(3, -1)
        let t = data[0]
        let l = data[1]
        let v = data.slice(2)
        if (t == '02') {
            if (v.length !== parseInt(l, 16)) {
                log.error("ble info data err")
                return
            }
            t = v[0]
            l = v[1]
            let bleName = ""
            if (l !== "00") {
                let v1 = v.slice(2, 2 + parseInt(l, 16))
                if (t !== '01') {
                    log.error("ble info data err")
                    return event
                }
                bleName = common.hexToString(v1.join(''))
            }
            if (bleName) {
                res.name = bleName
            }
            v = v.slice(2 + parseInt(l, 16))

            t = v[0]
            l = v[1]
            let v2 = v.slice(2)
            if (t !== '02') {
                log.error("ble info data err")
                return
            }
            let bleMac = v2.join('')
            if (bleMac) {
                res.mac = bleMac
            }
            map.put("__ble__config", res)
        } else if (t == '01') {
            return
        } else {
            log.error("ble info data err")
            return
        }
    } else {
        log.info("other ble info data")
    }
}

function CMDfa(pack) {
    let data = pack.data
    let index = data[pack.dlen - 1].toString(16).padStart(2, '0');
    // Record connection identifier
    log.info("index:" + index);
    if(data[0] == 0xFA && data[1] == 0xFB){
        // This indicates that this is the feature information that has been read.
        bus.fire("characterRead", pack)
    }
    return
}

try {
    run()
} catch (error) {
    log.error(error, error.stack)
}